using System;
using System.Threading;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;

namespace eLoop;

unsafe abstract class ASingleScheduler : ITaskScheduler
{
    private long refCount;
    private ConcurrentQueue<Work<object>> requests = new ConcurrentQueue<Work<object>>();

    private long isDisposed;
    
    
    public string Id { get; protected set; }

    public int ThreadId { get; protected set; }

    public string Name { get; protected set; }

    public long RefCount
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get { return Interlocked.Read(ref this.refCount); }
    }

    public bool IsDisposed
    {
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        get { return Interlocked.Read(ref this.isDisposed) == 1; }
    }
    
    protected virtual void InitializationSyncContext()
    {
    }
    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    protected bool IsEmpty()
    {
        return this.requests.IsEmpty;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    protected void Add(object callback1, delegate*<object, void> callback2, object o)
    {
        this.requests.Enqueue(new Work<object>(callback1, callback2, o));
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    protected bool TryGet(out Work<object> item)
    {
        return this.requests.TryDequeue(out item);
    }

    public void Reference()
    {
        if (this.IsDisposed)
        {
            return;
        }

        Interlocked.Increment(ref refCount);
    }

    public void Unreference()
    {
        if (this.IsDisposed)
        {
            return;
        }

        Interlocked.Decrement(ref refCount);
    }
    
    public bool IsEquals(ITaskScheduler scheduler)
    {
        return Scheduler.Equals(this, scheduler);
    }
    
    public void Schedule(IRunnable runnable, object o)
    {
        this.InternalSchedule(runnable, null, o);
    }

    public void Schedule(delegate*<object, void> action, object o)
    {
        this.InternalSchedule(null, action, o);
    }

    public void Schedule(IThreadPoolExecutor executor)
    {
        this.InternalSchedule(executor, null, null);
    }

    public void Schedule0(WaitCallback action, object o)
    {
        this.InternalSchedule(action, null, o);
    }

    public void Schedule1(Action<object> action, object o)
    {
        this.InternalSchedule(action, null, o);
    }

    internal void Schedule2(SendOrPostCallback action, object o)
    {
        this.InternalSchedule(action, null, o);
    }

    private void InternalSchedule(object callback1, delegate*<object, void> callback2, object o)
    {
        if (callback1 == null && callback2 == null)
        {
            throw new ArgumentNullException("The callback that needs to be scheduled is empty");
        }

        if (this.IsDisposed)
        {
            return;
        }

        this.Add(callback1, callback2, o);

        this.ExecuteLoop();
    }

    protected abstract void ExecuteLoop();

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    protected void DeliverWorks()
    {
        while (this.TryGet(out var item))
        {
            switch (item.callback1)
            {
                case Action<object> callback0:
                    callback0(item.state);
                    break;
                case WaitCallback callback1:
                    callback1(item.state);
                    break;
                case SendOrPostCallback callback2:
                    callback2(item.state);
                    break;
                case IRunnable runnable:
                    runnable.Execute(item.state);
                    break;
                case IThreadPoolExecutor executor:
                    executor.Execute();
                    break;
                default:
                    item.callback2(item.state);
                    break;
            }
        }
    }

    public virtual void Dispose()
    {
        Interlocked.Exchange(ref this.isDisposed, 1);

        this.refCount = 0;

#if NETCOREAPP
        this.requests.Clear();
#endif

#if NETSTANDARD2_0 || NETSTANDARD2_1
        while (this.TryGet(out _))
        {
        }
#endif
    }
}
